package org.microframework.cloud.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.microframework.cloud.gateway.config.AccessControlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 访问控制过滤器
 * 实现IP黑白名单和路径黑白名单功能
 */
@Slf4j
@Component
public class AccessControlFilter implements GlobalFilter, Ordered {

    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private AccessControlProperties accessControlProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 如果未启用访问控制，直接放行
        if (!accessControlProperties.isEnabled()) {
            return chain.filter(exchange);
        }

        ServerHttpRequest request = exchange.getRequest();
        String clientIp = getClientIp(request);
        String path = request.getURI().getPath();

        // 检查IP黑名单
        if (isIpBlocked(clientIp)) {
            return blockRequest(exchange, 
                    accessControlProperties.getIpBlacklist().getMessage(), 
                    "IP黑名单拦截: " + clientIp);
        }

        // 检查路径黑名单
        if (isPathBlocked(path)) {
            return blockRequest(exchange, 
                    accessControlProperties.getPathBlacklist().getMessage(), 
                    "路径黑名单拦截: " + path);
        }

        // 检查IP白名单
        boolean ipWhitelisted = isIpWhitelisted(clientIp);
        
        // 检查路径白名单
        boolean pathWhitelisted = isPathWhitelisted(path);
        
        // 如果IP白名单和路径白名单都启用了，且当前IP和路径都不在白名单中，则拦截请求
        if (accessControlProperties.getIpWhitelist().isEnabled() 
                && accessControlProperties.getPathWhitelist().isEnabled()
                && !ipWhitelisted && !pathWhitelisted) {
            return blockRequest(exchange, "访问受限", "IP和路径均不在白名单中: " + clientIp + ", " + path);
        }
        
        // 如果只启用了IP白名单，且当前IP不在白名单中，则拦截请求
        if (accessControlProperties.getIpWhitelist().isEnabled() 
                && !accessControlProperties.getPathWhitelist().isEnabled()
                && !ipWhitelisted) {
            return blockRequest(exchange, "IP访问受限", "IP不在白名单中: " + clientIp);
        }
        
        // 如果只启用了路径白名单，且当前路径不在白名单中，则拦截请求
        if (!accessControlProperties.getIpWhitelist().isEnabled() 
                && accessControlProperties.getPathWhitelist().isEnabled()
                && !pathWhitelisted) {
            return blockRequest(exchange, "路径访问受限", "路径不在白名单中: " + path);
        }

        // 设置白名单标记，可供后续过滤器使用
        if (ipWhitelisted || pathWhitelisted) {
            exchange = exchange.mutate()
                    .request(builder -> builder.header("X-Whitelist", "true"))
                    .build();
            
            // 如果配置了白名单跳过认证，则设置跳过认证标记
            if ((ipWhitelisted && accessControlProperties.getIpWhitelist().isBypassAuth()) 
                    || (pathWhitelisted && accessControlProperties.getPathWhitelist().isBypassAuth())) {
                exchange = exchange.mutate()
                        .request(builder -> builder.header("X-Skip-Auth", "true"))
                        .build();
            }
            
            // 如果配置了路径白名单跳过限流，则设置跳过限流标记
            if (pathWhitelisted && accessControlProperties.getPathWhitelist().isBypassRateLimit()) {
                exchange = exchange.mutate()
                        .request(builder -> builder.header("X-Skip-RateLimit", "true"))
                        .build();
            }
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 设置为最高优先级，在所有其他过滤器之前执行
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 获取客户端IP地址
     */
    private String getClientIp(ServerHttpRequest request) {
        String ip = null;
        
        // 尝试从X-Forwarded-For获取
        List<String> xForwardedFor = request.getHeaders().get("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            // 取第一个IP，即真实客户端IP
            ip = xForwardedFor.get(0);
            if (ip.contains(",")) {
                ip = ip.split(",")[0].trim();
            }
        }
        
        // 尝试从X-Real-IP获取
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            List<String> xRealIp = request.getHeaders().get("X-Real-IP");
            if (xRealIp != null && !xRealIp.isEmpty()) {
                ip = xRealIp.get(0);
            }
        }
        
        // 如果还是获取不到，则使用远程地址
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = Optional.ofNullable(request.getRemoteAddress())
                    .map(address -> address.getAddress())
                    .map(inetAddress -> inetAddress.getHostAddress())
                    .orElse("unknown");
        }
        
        return ip;
    }

    /**
     * 检查IP是否在黑名单中
     */
    private boolean isIpBlocked(String clientIp) {
        if (!accessControlProperties.getIpBlacklist().isEnabled()) {
            return false;
        }
        
        List<String> blacklist = accessControlProperties.getIpBlacklist().getIps();
        if (blacklist == null || blacklist.isEmpty()) {
            return false;
        }
        
        // 简单匹配，这里可以扩展为CIDR匹配
        return blacklist.contains(clientIp);
    }
    
    /**
     * 检查IP是否在白名单中
     */
    private boolean isIpWhitelisted(String clientIp) {
        if (!accessControlProperties.getIpWhitelist().isEnabled()) {
            return false;
        }
        
        // 如果配置了忽略本地地址，且是本地地址，则视为在白名单中
        if (accessControlProperties.getIpWhitelist().isIgnoreLocalhost() 
                && ("127.0.0.1".equals(clientIp) || "0:0:0:0:0:0:0:1".equals(clientIp))) {
            return true;
        }
        
        List<String> whitelist = accessControlProperties.getIpWhitelist().getIps();
        if (whitelist == null || whitelist.isEmpty()) {
            return false;
        }
        
        // 简单匹配，这里可以扩展为CIDR匹配
        return whitelist.contains(clientIp);
    }
    
    /**
     * 检查路径是否在黑名单中
     */
    private boolean isPathBlocked(String path) {
        if (!accessControlProperties.getPathBlacklist().isEnabled()) {
            return false;
        }
        
        List<String> blacklist = accessControlProperties.getPathBlacklist().getPaths();
        if (blacklist == null || blacklist.isEmpty()) {
            return false;
        }
        
        // 使用Ant风格路径匹配
        for (String pattern : blacklist) {
            if (pathMatcher.match(pattern, path)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 检查路径是否在白名单中
     */
    private boolean isPathWhitelisted(String path) {
        if (!accessControlProperties.getPathWhitelist().isEnabled()) {
            return false;
        }
        
        List<String> whitelist = accessControlProperties.getPathWhitelist().getPaths();
        if (whitelist == null || whitelist.isEmpty()) {
            return false;
        }
        
        // 使用Ant风格路径匹配
        for (String pattern : whitelist) {
            if (pathMatcher.match(pattern, path)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 拦截请求并返回错误信息
     */
    private Mono<Void> blockRequest(ServerWebExchange exchange, String message, String logMessage) {
        if (accessControlProperties.getSecurityConfig().isLogEnabled()) {
            String logLevel = accessControlProperties.getSecurityConfig().getLogLevel();
            switch (logLevel) {
                case "ERROR":
                    log.error(logMessage);
                    break;
                case "WARN":
                    log.warn(logMessage);
                    break;
                default:
                    log.info(logMessage);
            }
        }
        
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FORBIDDEN);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        Map<String, Object> responseBody = new HashMap<>();
        responseBody.put("code", accessControlProperties.getSecurityConfig().getBlockResponse().getCode());
        responseBody.put("message", message);
        responseBody.put("success", false);
        
        if (accessControlProperties.getSecurityConfig().getBlockResponse().isShowDetail()) {
            responseBody.put("path", exchange.getRequest().getURI().getPath());
            responseBody.put("timestamp", System.currentTimeMillis());
        }
        
        try {
            byte[] bytes = objectMapper.writeValueAsBytes(responseBody);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(buffer));
        } catch (Exception e) {
            log.error("生成错误响应失败", e);
            byte[] bytes = "{\"code\":403,\"message\":\"访问受限\",\"success\":false}".getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(buffer));
        }
    }
} 